/* eslint-disable max-len */
// 之所以写CharTween，因为egret.Tweeen对多个属性变化的处理不佳
// 一个变化，再来一个可以RemoveTween(obj)，否则后面的无法覆盖前面的！

interface TweenChar extends egret.DisplayObject {
	setPos(x: number, y: number): void;
}

class ObjTween {
	obj: TweenChar;

	tweens: CharTweens;

	constructor(obj: TweenChar, tweens: CharTweens) {
		this.obj = obj;
		this.tweens = tweens;
	}

	// tween(from: any, to: any, time: number, func?: Function, param?: any): void {}

	doUpdate(delta: number): void {}

	isMoving(): boolean {
		return false;
	}

	stop(callback: boolean): void {}
}

type reachCallback = (rt: number, why: string) => void;
// 通过MoveSprite.doUpdate来驱动，所以仅仅适用于地图上的物体！
class ObjMoveTween extends ObjTween {
	sx = 0;

	sy = 0;

	ex = 0;

	ey = 0;

	speed = 0;

	duration = 0;

	time = 0;

	dis = 0;

	getDes(): [number, number] {
		return [this.ex, this.ey];
	}

	callbackReach: reachCallback;

	moveTo(px: number, py: number, speed: number, dis: number, onReachFunc: reachCallback): void {
		assertObjPos(this.obj);
		this.sx = this.obj.x;
		this.sy = this.obj.y;
		this.ex = px;
		this.ey = py;
		this.speed = speed;
		this.time = 0;
		this.dis = dis;
		// const x1 = px - this.obj.x;
		// const y1 = py - this.obj.y;
		// const len = Math.sqrt(x1 * x1 + y1 * y1);
		const len = miscUtils.distanceY(px, py, this.obj.x, this.obj.y);
		this.duration = Math.ceil((len / speed) * 1000);
		assertNumbers([this.sx, this.sy, px, py, speed, this.duration]);
		// console.log("moveTo",this.sx,this.sy,this.ex,this.ey,this.duration);
		if (this.callbackReach) {
			this.callbackReach(0, 'break');
		}
		this.callbackReach = onReachFunc;
	}

	stopMove(reached: boolean, callback: boolean): void {
		this.speed = 0;
		const f = this.callbackReach;
		this.callbackReach = null;
		if (callback && f) {
			f(reached ? 1 : 0, 'stop');
		}
	}

	override stop(callback: boolean): void {
		this.stopMove(false, callback);
	}

	override isMoving(): boolean {
		return this.speed > 0;
	}

	onPosChange(): void {
		// if (this.obj.onPosChanged) {
		//     this.obj.onPosChanged.apply(this.obj);
		// }
	}

	CalctMovingTime(): number {
		return this.duration - this.time;
	}

	override doUpdate(delta: number): void {
		if (this.isMoving()) {
			this.time += delta;
			if (this.time >= this.duration) {
				this.obj.setPos(this.ex, this.ey);
				// this.obj.x = this.ex;
				// this.obj.y = this.ey;
				// this.onPosChange();
				this.assertxy();
				this.stopMove(true, true);
			} else {
				const tmp = this.time / this.duration;
				// this.obj.x = this.sx + (this.ex - this.sx) * tmp;
				// this.obj.y = this.sy + (this.ey - this.sy) * tmp;
				this.obj.setPos(
					this.sx + (this.ex - this.sx) * tmp,
					this.sy + (this.ey - this.sy) * tmp,
				);
				// this.onPosChange();
				this.assertxy();
				if (this.dis > 0) {
					if (miscUtils.distanceIn(this.obj.x, this.obj.y, this.ex, this.ey, this.dis)) {
						this.time = this.duration;
						this.stopMove(true, true);
					}
				}
			}
		}
	}

	assertxy(): void {
		// console.log("this.obj",this.obj.x,this.obj.y)
		assertObjPos(this.obj);
	}
}

class ObjFalldownTween extends ObjTween {
	v = 0;

	g = 0;

	char: game.MoveSprite;

	z0 = 0;

	z1 = 0;

	time = 0;

	action = '';

	downaction = '';

	fallDown(
		char: game.MoveSprite,
		v: number,
		g: number,
		z1: number,
		action: string,
		callback: reachCallback,
	): void {
		// let t =
		this.v = v;
		this.g = g;
		this.char = char;
		this.z0 = char.getZ();
		this.z1 = z1;
		this.action = action;
		this.cb = callback;
		this.time = 0;
	}

	cb: reachCallback;

	override stop(): void {
		this.char = null;
		if (this.cb) {
			const { cb } = this;
			this.cb = null;
			cb(1, 'stop');
		}
	}

	override isMoving(): boolean {
		return this.char != null;
	}

	override doUpdate(delta: number): void {
		if (this.isMoving()) {
			this.time += delta;
			const sec = this.time / 1000;
			const z = this.v * sec + this.z0 - (1 / 2) * this.g * (sec * sec);
			if (z <= this.z1) {
				this.char.setZ(this.z1);
				this.char.onJumpEnd(this.action);
				this.stop();
			} else {
				this.char.setZ(z);
			}
		}
	}
}

class ObjJumpTween extends ObjTween {
	speed0 = 0;

	time = 0;

	g = 0;

	char: game.MoveSprite;

	z0 = 0;

	z1 = 0;

	callbackDown = false;

	// callbackFall = false;

	nodown = false;

	jumptime = 0;

	action = '';

	downaction = '';

	layer = 0;

	topchange = false;

	jumpUp(
		char: game.MoveSprite,
		speed0: number,
		g: number,
		z1: number,
		nodown: boolean,
		action: string,
		downaction: string,
		time: number,
		layerObs: number,
		topchange: boolean,
		callback: reachCallback,
	): void {
		// let t =
		this.g = g;
		this.speed0 = speed0;
		this.time = 0;
		this.char = char;
		this.z0 = char.getZ();
		this.z1 = z1;
		this.nodown = nodown;
		this.action = action;
		this.downaction = downaction;
		this.jumptime = time;
		this.layer = layerObs;
		this.topchange = topchange;
		this.cb = callback;
	}

	cb: reachCallback;

	override stop(): void {
		this.speed0 = 0;
		this.char = null;
		this.callbackDown = false;
		// this.callbackFall = false;
		if (this.cb) {
			const { cb } = this;
			this.cb = null;
			cb(1, 'stop');
		}
	}

	inJump(): boolean {
		return this.isMoving();
	}

	inJumpDown(): boolean {
		return this.isMoving() && this.callbackDown;
	}

	override isMoving(): boolean {
		return this.char != null;
		// return this.speed0 !== 0;
	}

	CalcSpeed0(char: game.MoveSprite, time: number, g: number, deltaz: number): number {
		const z0 = char.getZ();
		const sec = time / 1000;

		// h=v0*t-(1/2)*g*t^2 ==> v0 = (h + (1/2)*g*t^2) / t
		return (-z0 + deltaz + (1 / 2) * g * sec * sec) / sec;
	}

	override doUpdate(delta: number): void {
		if (!this.isMoving()) {
			return;
		}
		this.time += delta;
		if (this.jumptime > 0 && this.time >= this.jumptime) {
			this.char.onJumpEnd(this.action);
			this.stop();
			return;
		}
		const sec = this.time / 1000;
		const z = this.z0 + this.speed0 * sec - (1 / 2) * this.g * (sec * sec);
		if (this.callbackDown && z <= this.z1) {
			if (this.layer >= 0) {
				this.char.setLayer(this.layer);
			}
			this.char.setZ(this.z1);
			this.char.onJumpEnd(this.action);
			this.stop();
		} else {
			this.char.setZ(z);
			if (sec > this.speed0 / this.g && !this.callbackDown) {
				// console.log("onJumpDown",sec,this.speed0,this.g)
				this.callbackDown = true;
				if (this.layer >= 0 && this.topchange) {
					this.char.setLayer(this.layer);
				}
				this.char.onJumpDown(this.downaction);
				if (this.nodown) {
					this.char.onJumpEnd(this.action);
					this.stop();
				}
			}
			// if (!this.callbackFall && this.callbackDown && 2 * (this.speed0 / this.g) - sec <= 0.2) {
			// 	this.callbackFall = true;
			// 	this.char.onJumpFall();
			// }
		}
	}
}

// class ObjFollowTween extends ObjTween {
// 	// SimplePoint: number[][];

// 	speed: number;

// 	dis = 0;

// 	onWP(wp: SimplePoint[], func: reachCallback): void {
// 		if (wp.length === 0) {
// 			func(1);
// 			this.speed = 0;
// 			return;
// 		}
// 		// let wp = this.wp;
// 		const pt = wp.shift();
// 		this.tweens.move.moveTo(pt.x, pt.y, this.speed, this.dis, (rt: number): void => {
// 			if (rt) {
// 				this.onWP(wp, func);
// 			} else {
// 				func(0);
// 				this.speed = 0;
// 			}
// 		});
// 	}

// 	followPath(wp: SimplePoint[], speed: number, callback: reachCallback): void {
// 		// this.wp = wp;
// 		this.speed = speed;
// 		this.onWP(wp, callback);
// 		// this.callbackPath = callback;
// 	}

// 	isMoving(): boolean {
// 		return this.speed > 0;
// 	}

// 	stop(callback: boolean): void {
// 		this.speed = 0;
// 	}
// }

class ObjCloneTween extends ObjTween {
	override obj: game.MapSprite;

	cloneTime = 0;

	cloneLife: number;

	cloneAlpha: number;

	cloneAlphamin: number;

	clonePass = 0;

	startClone(clonetime: number, life: number, alpha: number, alphamin: number): void {
		this.cloneTime = clonetime;
		this.cloneLife = life;
		this.cloneAlpha = alpha;
		this.cloneAlphamin = alphamin;
		this.clonePass = 0;
		this.num = 0;
	}

	override stop(): void {
		this.cloneTime = 0;
	}

	cloneObj(): game.MoveSprite {
		const c = this.obj;
		// if (c instanceof game.MoveSprite) {
		const obj = c.clonePart(0, 'all', true);
		obj.touchEnabled = false;
		obj.touchChildren = false;
		obj.alpha = this.cloneAlpha;
		return obj;
	}

	override isMoving(): boolean {
		return this.cloneTime > 0;
	}

	num = 0;

	override doUpdate(delta: number): void {
		if (this.cloneTime > 0) {
			this.clonePass += delta;
			if (this.clonePass > this.cloneTime) {
				this.clonePass = 0;
				const c = this.cloneObj();
				if (!c) {
					return;
				}
				this.num++;
				// this.obj.parent.addChild(c);
				c.tween.fade.fadeOut(this.cloneAlphamin, this.cloneLife, () => {
					this.num--;
					safeRemoveFromFather(c);
				});
			}
		}
	}
}

class ObjTweenData extends ObjTween {
	name: string;

	start: number;

	end: number;

	func: string;

	time = 0;

	cb: reachCallback;

	delta = 0;

	override isMoving(): boolean {
		return this.time > 0;
	}

	override stop(callback: boolean): void {
		this.time = 0;
		if (callback) {
			this.callback(0);
		}
		this.tweens.delTweenInLoop(this.name);
	}

	callback(rt: number): void {
		const { cb } = this;
		this.cb = null;
		if (cb) {
			cb(rt, 'ok');
		}
	}

	to(
		name: string,
		start: number,
		end: number,
		time: number,
		func: string,
		callback: reachCallback,
	): void {
		this.name = name;
		this.start = start;
		this.end = end;
		this.time = time;
		this.func = func;
		this.delta = 0;
		this.cb = callback;
	}

	override doUpdate(delta: number): void {
		if (this.time > 0) {
			this.delta += delta;
			if (this.delta >= this.time) {
				this.obj[this.name] = this.end;
				this.callback(1);
				this.stop(true);
			} else {
				this.obj[this.name] =
					this.start + (this.end - this.start) * (this.delta / this.time);
				// console.warn('this.obj.alpha', this.obj.alpha, this.start, this.end, this.delta, this.time);
			}
		}
	}
}

class ObjFadeTween extends ObjTweenData {
	override stop(callback: boolean): void {
		this.time = 0;
		if (callback && this.cb) {
			this.callback(0);
		}
		// this.tweens.delTween(this.name, false);
	}

	fadeOut(end: number, time: number, callback: reachCallback): void {
		this.to('alpha', this.obj.alpha, end, time, '', callback);
	}
}

class ObjLightTween extends ObjTween {
	override obj: game.MapSprite;

	time: number;

	delta = 0;

	end = 0;

	start: number;

	override stop(callback: boolean): void {
		this.time = 0;
	}

	changeLightDir(dir: number, time: number): void {
		const angle = dir2Angle[dir];
		// if (this.obj instanceof game.MoveSprite) {
		const { day: light } = this.obj.light;
		if (light && light instanceof RoundLight) {
			const all = light.endAngle - light.startAngle;
			this.start = light.startAngle;
			this.end = angle - all / 2;

			let delta = this.end - this.start;
			while (delta > 180) {
				this.end -= 360;
				delta -= 360;
			}
			while (delta < -180) {
				this.end += 360;
				delta += 360;
			}

			this.time = time;
			this.delta = 0;
			if (time === 0) {
				light.startAngle = this.end;
				light.endAngle = light.startAngle + all;
				light.updateGraph();
			}
		}
		// }
	}

	doUpdateLight(light: RoundLight): void {
		const all = light.endAngle - light.startAngle;
		if (this.delta >= this.time) {
			light.startAngle = this.end;
			this.stop(true);
		} else {
			light.startAngle = this.start + (this.end - this.start) * (this.delta / this.time);
		}
		light.endAngle = light.startAngle + all;
		light.updateGraph();
	}

	override doUpdate(delta: number): void {
		// if (this.obj instanceof game.MoveSprite) {
		const { light } = this.obj;
		// const { ghost } = this.obj;
		if (this.time > 0) {
			this.delta += delta;
			if (light.day instanceof RoundLight) this.doUpdateLight(light.day);
			if (light.dark instanceof RoundLight) this.doUpdateLight(light.dark);
		}
		// }
	}
}

class CharTweens {
	obj: game.MoveSprite;

	tweens: { [key: string]: ObjTween } = {};

	move: ObjMoveTween;

	jump: ObjJumpTween;

	fall: ObjFalldownTween;

	clone: ObjCloneTween;

	fade: ObjFadeTween;

	// follow: ObjFollowTween;

	light: ObjLightTween;

	ghost: ObjLightTween;

	constructor(obj: game.MoveSprite) {
		this.obj = obj;
		this.move = new ObjMoveTween(obj, this);
		this.jump = new ObjJumpTween(obj, this);
		this.fall = new ObjFalldownTween(obj, this);
		// this.follow = new ObjFollowTween(obj, this);
		this.clone = new ObjCloneTween(obj, this);
		this.fade = new ObjFadeTween(obj, this);
		this.light = new ObjLightTween(obj, this);
		this.ghost = new ObjLightTween(obj, this);
		this.tweens = {
			move: this.move,
			jump: this.jump,
			fall: this.fall,
			// follow: this.follow,
			clone: this.clone,
			fade: this.fade,
			light: this.light,
			ghost: this.ghost,
		};
	}

	stopAll(callback: boolean): void {
		for (const [_, tw] of Object.entries(this.tweens)) {
			tw.stop(callback);
		}
		// this.tweens = [];
	}

	doUpdate(delta: number): void {
		for (const [_, tw] of Object.entries(this.tweens)) {
			tw.doUpdate(delta);
		}
	}

	// dynamic tween!
	addTween(name: string, tw: ObjTween): void {
		this.tweens[name] = tw;
	}

	getTween(name: string): ObjTween {
		return this.tweens[name];
	}

	delTween(name: string, callback: boolean): void {
		const tw = this.tweens[name];
		if (tw) {
			tw.stop(callback);
			delete this.tweens[name];
		}
	}

	delTweenInLoop(name: string): void {
		const tw = this.tweens[name];
		if (tw) {
			delete this.tweens[name];
		}
	}
}
